package pl.edu.agh.intobl;

import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

import org.joone.engine.FullSynapse;
import org.joone.engine.Layer;
import org.joone.engine.LinearLayer;
import org.joone.engine.Monitor;
import org.joone.engine.NeuralNetListener;
import org.joone.engine.SigmoidLayer;
import org.joone.engine.learning.TeachingSynapse;
import org.joone.io.FileInputSynapse;
import org.joone.io.FileOutputSynapse;
import org.joone.net.NeuralNet;
import org.joone.net.NeuralNetLoader;

public class NetworkBuilder {

	private static final int CYCLES = 10000;
	private static final double LEARNING_RATE = 0.8;
	private static final double MOMENTUM = 0.3;
	private final String inputFilename;
	private NeuralNet network;
	private NeuralNetListener listener;

	public NetworkBuilder(String inputFile) {
		this.inputFilename = inputFile;
		this.listener = new NetworkListener();
	}

	public NeuralNet buildNetwork() {
		network = new NeuralNet();

		// input
		FileInputSynapse fileInputSynapse = new FileInputSynapse();
		/* The first two columns contain the input values */
		fileInputSynapse.setAdvancedColumnSelector("1,2");
		File file = new File(inputFilename);
		fileInputSynapse.setInputFile(file);

		// Layers
		LinearLayer inputLayer = new LinearLayer("input");
		SigmoidLayer hiddenLayer = new SigmoidLayer("hidden");
		SigmoidLayer outputLayer = new SigmoidLayer("output");
		inputLayer.setRows(2);
		hiddenLayer.setRows(3);
		outputLayer.setRows(1);

		// Synapses
		FullSynapse inToHidSynapse = new FullSynapse();
		FullSynapse hidToOutSynapse = new FullSynapse();

		inputLayer.addInputSynapse(fileInputSynapse);

		inputLayer.addOutputSynapse(inToHidSynapse);
		hiddenLayer.addInputSynapse(inToHidSynapse);

		hiddenLayer.addOutputSynapse(hidToOutSynapse);
		outputLayer.addInputSynapse(hidToOutSynapse);

		network.addLayer(inputLayer, NeuralNet.INPUT_LAYER);
		network.addLayer(hiddenLayer, NeuralNet.HIDDEN_LAYER);
		network.addLayer(outputLayer, NeuralNet.OUTPUT_LAYER);

		// Monitor
		Monitor monitor = network.getMonitor();
		monitor.setLearningRate(LEARNING_RATE);
		monitor.setMomentum(MOMENTUM);
		monitor.addNeuralNetListener(listener);
		monitor.setTrainingPatterns(4); /* # of rows in the input file */
		monitor.setTotCicles(CYCLES); /* How many times the net must be trained */
		monitor.setLearning(true); /* The net must be trained */

		// Learning
		TeachingSynapse supervisor = new TeachingSynapse();
		FileInputSynapse correctResponses = new FileInputSynapse();
		correctResponses.setInputFile(file);
		correctResponses.setAdvancedColumnSelector("3");
		supervisor.setDesired(correctResponses);
		outputLayer.addOutputSynapse(supervisor);
		network.setTeacher(supervisor);

		return network;
	}

	public void saveNeuralNetwork(String fileName) {
		try {
			FileOutputStream stream = new FileOutputStream(fileName);
			ObjectOutputStream out = new ObjectOutputStream(stream);
			out.writeObject(network);
			out.close();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
	
	public void loadNeuralNetwork(String fileName){
		NeuralNetLoader loader = new NeuralNetLoader(fileName);
		network = loader.getNeuralNet();
	}

	public void modifyToServingResults(String filename) {
		// we get the output layer
		Layer output = network.getOutputLayer();
		// we create an output synapse
		FileOutputSynapse fileOutput = new FileOutputSynapse();
		fileOutput.setFileName(filename);
		// we attach the output synapse to the last layer of the NN
		output.addOutputSynapse(fileOutput);
		// we run the neural network only once (1 cycle) in recall mode
		network.getMonitor().setTotCicles(1);		
		network.getMonitor().setLearning(false);
	}
}
